/**
 * File glfractal.cpp
 * Brief Implementation of GLFractal
 * This is a simple QGLWidget displaying an openGL wireframe box
 *
 * The OpenGL code is mostly borrowed from Brian Pauls "spin" example
 * in the Mesa distribution.
 * $ID$
 */

#include "GLWidget.h"

/**
 * Constructor that creates a GLFractal widget
 */
GLWidget::GLWidget( QWidget* parent) : QGLWidget( parent) {
	initialized = false;
	rows = numRows + subdivisions*(numRows-1);
	cols = numCols + subdivisions*(numCols-1);
	openGL = true;
}

GLWidget::~GLWidget() {}

/*--------------------------------------------------------------------------*/
/**
 * Paint the box. The actual openGL commands for drawing the box are
 * performed here.
 */
void GLWidget::paintGL()
{
	if (!openGL) return;
  /*glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glEnable( GL_DEPTH_TEST ); 
  glLoadIdentity();
  glTranslatef(0.0, 0.0, -10.0);
  // set the zoom according to the scale variable
  glScalef(scale, scale, scale);
  // set the image rotation up according to xRot, yRot, zRot
  glRotatef( xRot, 1.0, 0.0, 0.0);
  glRotatef( yRot, 0.0, 1.0, 0.0);
  glRotatef( zRot, 0.0, 0.0, 1.0);

  glCallList(object);*/

        glClearColor(0.0, 0.0, 0.0, 1.0);
        glEnable(GL_DEPTH_TEST);
        //glEnable(GL_CULL_FACE);
		glEnable(GL_COLOR_MATERIAL);
        glViewport( 0, 0, width(), height() );
        //from barebonse
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();

        if(width()>=height()){
            float fc = width()/(float)height();
            glFrustum( -1.0*fc, 1.0*fc, -1.0, 1.0, 2.0, 10000.0 );
			/*camera.leftSide = -1.0*fc;
			camera.rightSide = 1.0*fc;
			camera.bottom = -1.0;
			camera.top = 1.0;
			camera.nearPlane = 2.0;
			camera.farPlane = 10000.0;*/
        }
        else if(height()>width()){
            float fc = height()/(float)width();
            glFrustum( -1.0, 1.0, -1.0*fc, 1.0*fc, 2.0, 10000.0 );
			/*camera.leftSide = -1.0;
			camera.rightSide = 1.0;
			camera.bottom = -1.0*fc;
			camera.top = 1.0*fc;
			camera.nearPlane = 2.0;
			camera.farPlane = 10000.0;*/
        }
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        GLfloat diffColor[] = {0.8f, 0.8f, 0.5f, 1.0f};
        GLfloat specColor[] = {1.0f, 1.0f, 0.9f, 1.0f};
        GLfloat ambColor[] = {0.3f, 0.3f, 0.3f, 1.0f};
        glLightfv(GL_LIGHT0, GL_DIFFUSE, diffColor);
        glLightfv(GL_LIGHT0, GL_SPECULAR, specColor);
        glLightfv(GL_LIGHT1, GL_AMBIENT, ambColor);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        //glEnable(GL_LIGHT1);
        /*startDisplayList = glGenLists(gridWidth*gridDepth);
        if (startDisplayList == 0){
            cout << "Error creating display lists" << endl;
        }
        displayListIndicies = new GLubyte[gridWidth*gridDepth];
        for(int i = 0; i < gridWidth*gridDepth; i++){
            displayListIndicies[i] = i;
        }
        //now we can set the zone numbers
        for (int i = 0; i < gridDepth; i++) {
		    for (int j = 0; j < gridWidth; j++) {
                int zoneNum = i*gridWidth + j + startDisplayList;
                zoneGrid[i][j]->zoneNum = zoneNum;
		    }
	    }*/

   // } //initializing done, can draw now.

	//move the camera if necessary
	//camera.update(wDown, sDown, aDown, dDown);

	//draw
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //might change this to opengl 3+ method, this stuff runs off the cpu not gpu
    //glu is completed deprecated, maybe use a math library?
    glLoadIdentity(); //deprecated

	Vector3 cameraPos = scene->cameraLocation;
	Vector3 cameraDir = scene->cameraLookAt;
	gluLookAt(cameraPos.x, cameraPos.y, cameraPos.z,
			  cameraDir.x, cameraDir.y, cameraDir.z,
			  0, 1, 0);

	/*int spacer = subdivisions + 1;
	vector<vector<Vector3> > newPoints;
	for (int i = 0; i < numRows-1; i++) {
		for (int j = 0; j < numCols-1; j++) {
			subdivide(waterPoints[i*spacer][j*spacer].p, waterPoints[(i+1)*spacer][j*spacer].p, waterPoints[(i+1)*spacer][(j+1)*spacer].p, waterPoints[i*spacer][(j+1)*spacer].p, newPoints);

			for (int l = 0; l < newPoints.size(); l++) {
				for (int m = 0; m < newPoints[l].size(); m++) {
					//WaterPoint w;
					//w.p = newPoints[l][m];
					/*w.up = waterPoints[i*spacer+l][j*spacer+m].up;
					w.control = waterPoints[i*spacer+l][j*spacer+m].control;
					w.ratio = waterPoints[i*spacer+l][j*spacer+m].ratio;*/
					/*waterPoints[i*spacer+l][j*spacer+m].p = newPoints[l][m];
				}
			}
		}
	}*/

	vector<vector<Vector3> > tmp;
	tmp.resize(numRows);
	for (int i = 0; i < numRows; i++) {
		for (int j = 0; j < numCols; j++) {
			tmp[i].push_back(waterPoints[i][j].p);
		}
	}

	vector<vector<Vector3> > subdividedPoints;
	vector<vector<Vector3> > tmpSubdividedPoints;

	/*subdividedPoints.resize(numRows);
	for (int i = 0; i < numRows; i++) {
		for (int j = 0; j < numCols; j++) {
			subdividedPoints[i].push_back(waterPoints[i][j].p);
		}
	}*/

	
	//loopSubdivision(LOOP_SUBDIVISIONS, tmp, subdividedPoints);
	//regularSubdivision(REGULAR_SUBDIVISIONS, tmpSubdividedPoints, subdividedPoints);
	loopSubdivision(3, tmp, subdividedPoints);


	faceNormals.resize(subdividedPoints.size()-1);
	for (int i = 0; i < faceNormals.size(); i++) {
		faceNormals[i].resize((subdividedPoints[i].size()-1)*2);
	}

	vertexNormals.resize(subdividedPoints.size());
	for (int i = 0; i < vertexNormals.size(); i++) {
		vertexNormals[i].resize(subdividedPoints[i].size());
	}

	Vector3 v1, v2, v3, v4, n_1, n_2, n;

	for (int i = 0; i < subdividedPoints.size()-1; i++) {
		for (int j = 0; j < subdividedPoints[i].size()-1; j++) {
			v1 = subdividedPoints[i+1][j];
			v2 = subdividedPoints[i][j];
			v3 = subdividedPoints[i+1][j+1];
			v4 = subdividedPoints[i][j+1];

			n_1 = Vector3::crossProduct((v2-v1), (v3-v1));
			n_1.normalize();

			n_2 = Vector3::crossProduct((v3-v4), (v2-v4));
			n_2.normalize();

			faceNormals[i][j*2] = n_1;
			faceNormals[i][j*2+1] = n_2;
		}
	}

	float count;

	for (int i = 0; i < subdividedPoints.size(); i++) {
		for (int j = 0; j < subdividedPoints[i].size(); j++) {
			count = 0;
			n = Vector3(0, 0, 0);

			if (i > 0 && j*2 < faceNormals[i-1].size()) {
				n = n + faceNormals[i-1][j*2];
				count++;
			}
			if (j > 0 && i < faceNormals.size()) {
				n = n + faceNormals[i][j*2-1];
				count++;
			}
			if (i > 0 && j > 0) {
				n = n + faceNormals[i-1][j*2-1];
				count++;
			}
			if (i < faceNormals.size() && j*2 < faceNormals[i].size()) {
				n = n + faceNormals[i][j*2];
				count++;
			}

			if (i > 0 && j-1 > 0) {
				n = n + faceNormals[i-1][j*2-2];
				count++;
			}
			if (i < faceNormals.size() && j*2+1 < faceNormals[i].size()) {
				n = n + faceNormals[i][j*2+1];
				count++;
			}

			n = n * (1.0/count);
			n.normalize();

			vertexNormals[i][j] = n;
		}
	}

	scene->objects.clear();

	Vector3 min = Vector3(-(numCols-1)*g_CONTROL_SIZE, -HEIGHT, 0);
	Vector3 max = Vector3(0, HEIGHT, (numRows-1)*g_CONTROL_SIZE);
	//BoundingBox* bb = new BoundingBox(Vector3(-(numCols-1)*g_CONTROL_SIZE, -HEIGHT, 0), Vector3(0, HEIGHT, (numRows-1)*g_CONTROL_SIZE));
	BoundingBox* bb = new BoundingBox(min, max);
	bb->split(4);
	/*Vector3 t0(-100, -100, -40);
	Vector3 t1(100, -100, -40);
	Vector3 t2(0, 500, -40);
	Triangle *tri1 = new Triangle(Vector3(), 0, t2, t1, t0);
	tri1->material = Material(Color(1, 0, 0), Color(0.4, 0.4, 0.4), Color(0.4, 0.4, 0.4), 100);
	scene->objects.push_back(tri1);*/

	//min.y = POOL_FLOOR_HEIGHT;
	//max.y = POOL_FLOOR_HEIGHT;
	Vector3 mid = min*0.5 + max*0.5;
	mid.y = POOL_FLOOR_HEIGHT;
	

	//Quad* quad = new Quad(mid, Vector3(0, 1, 0), g_abs(min.x-max.x), g_abs(min.z-max.z));
	Quad* quad = new Quad(mid, Vector3(0, 1, 0), g_abs(min.x-max.x)*2, g_abs(min.z-max.z)*2); //*2 for now since no walls
	quad->material = Material(Color(1, 0.5, 1), Color(0.4, 0.4, 0.4), Color(0.1, 0.1, 0.1), 100);
	scene->objects.push_back(quad);

	createCausticTriangles(subdividedPoints);

	Vector3 n1, n2, n3, n4;

	glBegin(GL_TRIANGLES);
	glColor3f(0.2, 0.5, 1);
	//glColor3f(0.8, 0.2, 0.1);
	for (int i = 0; i < subdividedPoints.size()-1; i++) {
		for (int j = 0; j < subdividedPoints[i].size()-1; j++) {
			v1 = subdividedPoints[i][j];
			v2 = subdividedPoints[i+1][j];
			v3 = subdividedPoints[i+1][j+1];
			v4 = subdividedPoints[i][j+1];

			n1 = vertexNormals[i][j];
			n2 = vertexNormals[i+1][j];
			n3 = vertexNormals[i+1][j+1];
			n4 = vertexNormals[i][j+1];

			//scene->objects.push_back(new Triangle(faceNormals[i][j*2], 40, v1, v3, v2));
			//scene->objects.push_back(new Triangle(faceNormals[i][j*2+1], 40, v1, v4, v3));

			Triangle *tri1 = new Triangle(v1, n1, v3, n3, v2, n2);
			Triangle *tri2 = new Triangle(v1, n1, v4, n4, v3, n3);
			tri1->material = Material(Color(0.2, 0.5, 1), Color(0, 0, 0), Color(0.2, 0.5, 1), 100);
			tri2->material = Material(Color(0.2, 0.5, 1), Color(0, 0, 0), Color(0.2, 0.5, 1), 100);
			bb->addObject(tri1);
			bb->addObject(tri2);
			//scene->objects.push_back(tri1);
			//scene->objects.push_back(tri2);

			glNormal3f(n1.x, n1.y, n1.z);
			glVertex3f(v1.x, v1.y, v1.z);
			glNormal3f(n3.x, n3.y, n3.z);
			glVertex3f(v3.x, v3.y, v3.z);
			glNormal3f(n2.x, n2.y, n2.z);
			glVertex3f(v2.x, v2.y, v2.z);
			
			glNormal3f(n1.x, n1.y, n1.z);
			glVertex3f(v1.x, v1.y, v1.z);
			glNormal3f(n4.x, n4.y, n4.z);
			glVertex3f(v4.x, v4.y, v4.z);
			glNormal3f(n3.x, n3.y, n3.z);
			glVertex3f(v3.x, v3.y, v3.z);

			/*v1 = waterPoints[i+1][j].p;
			v2 = waterPoints[i][j].p;
			v3 = waterPoints[i+1][j+1].p;

			n = (Vector3(v2-v1)).cross(v3-v1);
			n.normalize();

			glNormal3f(n.x, n.y, n.z);
			glVertex3f(v1.x, v1.y, v1.z);
			glVertex3f(v2.x, v2.y, v2.z);
			glVertex3f(v3.x, v3.y, v3.z);


			v1 = waterPoints[i+1][j+1].p;
			v2 = waterPoints[i][j].p;
			v3 = waterPoints[i][j+1].p;

			n = (Vector3(v2-v1)).cross(v3-v1);
			n.normalize();

			glNormal3f(n.x, n.y, n.z);
			glVertex3f(v1.x, v1.y, v1.z);
			glVertex3f(v2.x, v2.y, v2.z);
			glVertex3f(v3.x, v3.y, v3.z);*/
		}
	}
	scene->objects.push_back(bb);
	glEnd();

    glFlush();
}

void GLWidget::update(float seconds, bool draw) {
	float waterSpeed = 1.7f;
	//float waterSpeed = 3.0f;
	//seconds = 1.0/20.0;

	WaterPoint w;
	for (int i = 0; i < waterPoints.size(); i++) {
		for (int j = 0; j < waterPoints[i].size(); j++) {
			w = waterPoints[i][j];

			if (!w.control) continue;

			if (w.p.y >= HEIGHT*w.ratio) {
				w.up = false;
				w.ratio = (float)g_random(70, 100)/100.0f;
			}
			else if (w.p.y <= -HEIGHT*w.ratio) {
				w.up = true;
				w.ratio = (float)g_random(70, 100)/100.0f;
			}

			float amount = seconds*waterSpeed;

			if (w.up) {
				w.p.y += amount;
			}
			else {
				w.p.y -= amount;
			}
			
			/*if (i == j == 0) {
				debug.writeToFile(w.p.y);
				debug.writeToFile(((float)milliseconds/1000.0f)*waterSpeed);
				debug.writeToFile("");
			}*/
			waterPoints[i][j] = w;
		}
	}

	if (draw) {
		updateGL();
	}
}

/*--------------------------------------------------------------------------*/
/**
 * Set up the OpenGL rendering state, and define display list
 */

void GLWidget::initializeGL()
{
	//only initialize once
	if (!initialized) {
		initialized = true;

		glClearColor( 0.0, 0.0, 0.0, 0.0 ); //OpenGL clears to black
		glShadeModel( GL_SMOOTH ); //shading (smooth or flat)

		
	}
}

/*--------------------------------------------------------------------------*/
/**
 * Set up the OpenGL view port, matrix mode, etc.
 */
void GLWidget::resizeGL( int w, int h )
{
  glViewport( 0, 0, (GLint)w, (GLint)h );
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  glFrustum(-1.0,1.0,-1.0,1.0,5.0,15.0);
  glMatrixMode( GL_MODELVIEW );
}

void GLWidget::subdivide(const Vector3& p00, const Vector3& p01, const Vector3& p11, const Vector3& p10, vector<vector<Vector3> >& newPoints) {
	/*Assumes:
			 p1
		p01 ---- p11
		 |	      |
	  q0 |		  | q1
		 |		  |
		p00 ---- p10
	 ^ 	     p0
	 |
	 uv->
	*/

	//set the sin values so they only have to be calculated once
	vector<float> sinValues(subdivisions+2);
	for (int i = 0; i < subdivisions+2; i++) {
		sinValues[i] = sin((PI/2)*((float)i/(subdivisions+1)));
	}

	//set function values (the amplitudes of the sin waves)
	float q0 = p01.y - p00.y;
	float q1 = p11.y - p10.y;
	float p0 = p10.y - p00.y;
	float p1 = p11.y - p01.y;

	//clear whatever data is in the newPoints vector
	newPoints.clear();
	newPoints.resize(subdivisions+2);

	//calculate the new points
	float u, v;
	for (int i = 0; i < subdivisions+2; i++) {
		u = (float)i/(subdivisions+1);
		for (int j = 0; j < subdivisions+2; j++) {
			v = (float)j/(subdivisions+1);

			Vector3 a;

			//add the two ruled surfaces
			a = a + (Vector3(p00) + p0*sinValues[j])*(1-u);
			a = a + (Vector3(p01) + p1*sinValues[j])*(u);
			a = a + (Vector3(p00) + q0*sinValues[i])*(1-v);
			a = a + (Vector3(p10) + q1*sinValues[i])*(v);
			
			//subtract the bilinear patch
			a = a - Vector3(p00)*(1-u)*(1-v);
			a = a - Vector3(p01)*(u)*(1-v);
			a = a - Vector3(p10)*(v)*(1-u);
			a = a - Vector3(p11)*(u)*(v);

			a.x = p00.x*(1-v) + p10.x*(v);
			a.z = p00.z*(1-u) + p01.z*(u);

			newPoints[i].push_back(a);
		}
	}
}

void GLWidget::loopSubdivision(int levels, vector<vector<Vector3> >& inPoints, vector<vector<Vector3> >& subdividedPoints) {
	if (levels == 0) {
		subdividedPoints.resize(inPoints.size());
		for (int i = 0; i < inPoints.size(); i++) {
			subdividedPoints[i].resize(inPoints[i].size());
			for (int j = 0; j < inPoints[i].size(); j++) {
				subdividedPoints[i][j] = inPoints[i][j];
			}
		}
		return;
	}

	subdividedPoints.resize(inPoints.size()*2-1);
	for (int i = 0; i < subdividedPoints.size(); i++) {
		subdividedPoints[i].resize(inPoints[0].size()*2-1);
		for (int j = 0; j < subdividedPoints[i].size(); j++) {
			//case 1: a corner
			//in this case, the vertex doesn't move at all
			if ((i == 0 && j == 0) || (i == 0 && j == subdividedPoints[i].size()-1) || (i == subdividedPoints.size()-1 && j == 0) || (i == subdividedPoints.size()-1 && j == subdividedPoints[i].size()-1)) {
				//i/2 and j/2 will be even in every case
				subdividedPoints[i][j] = inPoints[i/2][j/2];
			}

			//case 2: a vertical boundary edge-vertex
			//in this case, the new vertex is half the one above and below it
			else if ((j == 0 || j == subdividedPoints[i].size()-1) && (i%2 == 1)) {
				subdividedPoints[i][j] = inPoints[i/2][j/2]*0.5 + inPoints[i/2+1][j/2]*0.5;
			}

			//case 3: a horizontal boundary edge-vertex
			//in this case, the new vertex is half the one on either side of it
			else if ((i == 0 || i == subdividedPoints.size()-1) && (j%2 == 1)) {
				subdividedPoints[i][j] = inPoints[i/2][j/2]*0.5 + inPoints[i/2][j/2+1]*0.5;
			}

			//case 4: a vertical boundary vertex-vertex
			//in this case, the new vertex is 1/8 the one above and below it, and 3/4 the original one
			else if ((j == 0 || j == subdividedPoints[i].size()-1) && (i%2 == 0)) {
				subdividedPoints[i][j] = inPoints[i/2][j/2]*0.75 + inPoints[i/2+1][j/2]*0.125 + inPoints[i/2-1][j/2]*0.125;
			}

			//case 5: a horizontal boundary vertex-vertex
			//in this case, the new vertex is 1/8 the ones to either side of it, and 3/4 the original one
			else if ((i == 0 || i == subdividedPoints.size()-1) && (j%2 == 0)) {
				subdividedPoints[i][j] = inPoints[i/2][j/2]*0.75 + inPoints[i/2][j/2+1]*0.125 + inPoints[i/2][j/2-1]*0.125;
			}

			//case 6: a vertex-vertex
			//in this case, the new vertex is 1/8 the ones on all sides, and 1/2 the original one
			else if ((i%2 == 0) && (j%2 == 0)) {
				subdividedPoints[i][j] = inPoints[i/2][j/2]*0.5 + inPoints[i/2][j/2+1]*0.125 + inPoints[i/2][j/2-1]*0.125 + inPoints[i/2+1][j/2]*0.125 + inPoints[i/2-1][j/2]*0.125;
			}

			//case 7: a diagonal edge-vertex
			//in this case, the new vertex is 1/4 the ones on all sides
			else if ((i%2 == 1) && (j%2 == 1)) {
				subdividedPoints[i][j] = inPoints[i/2][j/2]*0.25 + inPoints[i/2][j/2+1]*0.25 + inPoints[i/2+1][j/2]*0.25 + inPoints[i/2+1][j/2+1]*0.25;
			}

			//case 8: a horizontal edge-vertex
			//in this case, the new vertex is 1/2 the ones on either side
			else if ((i%2 == 0) && (j%2 == 1)) {
				subdividedPoints[i][j] = inPoints[i/2][j/2]*0.5 + inPoints[i/2][j/2+1]*0.5;
			}

			//case 8: a vertical edge-vertex
			//in this case, the new vertex is 1/2 the ones above and below
			else if ((i%2 == 1) && (j%2 == 0)) {
				subdividedPoints[i][j] = inPoints[i/2][j/2]*0.5 + inPoints[i/2+1][j/2]*0.5;
			}

			//WE SHOULD NEVER HIT THIS CASE
			else {
				debug.writeToFile("ERROR!!!");
			}
		}
	}

	for (int i = 0; i < subdividedPoints.size(); i++) {
		for (int j = 0; j < subdividedPoints[i].size(); j++) {
			//case 1: a corner
			//in this case, the vertex doesn't move at all
			if ((i == 0 && j == 0) || (i == 0 && j == subdividedPoints[i].size()-1) || (i == subdividedPoints.size()-1 && j == 0) || (i == subdividedPoints.size()-1 && j == subdividedPoints[i].size()-1)) {
				//i/2 and j/2 will be even in every case
				subdividedPoints[i][j] = inPoints[i/2][j/2];
			}

			//case 8: a horizontal edge-vertex
			//in this case, the new vertex is 1/2 the ones on either side
			else if ((i%2 == 0) && (j%2 == 1)) {
				subdividedPoints[i][j] = subdividedPoints[i][j+1]*0.5 + subdividedPoints[i][j-1]*0.5;
			}

			//case 8: a vertical edge-vertex
			//in this case, the new vertex is 1/2 the ones above and below
			else if ((i%2 == 1) && (j%2 == 0)) {
				subdividedPoints[i][j] = subdividedPoints[i+1][j]*0.5 + subdividedPoints[i-1][j]*0.5;
			}
		}
	}

	levels--;
	if (levels > 0) {
		vector<vector<Vector3> > tmp;
		tmp.resize(subdividedPoints.size());
		for (int i = 0; i < tmp.size(); i++) {
			tmp[i].resize(subdividedPoints[i].size());
			for (int j = 0; j < tmp[i].size(); j++) {
				tmp[i][j] = subdividedPoints[i][j];
			}
		}

		loopSubdivision(levels, tmp, subdividedPoints);
	}
}

void GLWidget::regularSubdivision(int levels, vector<vector<Vector3> >& inPoints, vector<vector<Vector3> >& subdividedPoints) {
	float u, v;

	subdividedPoints.resize(inPoints.size() + (inPoints.size()-1)*levels);
	for (int i = 0; i < subdividedPoints.size(); i++) {
		subdividedPoints[i].resize(inPoints.size() + (inPoints.size()-1)*levels);
	}
	
	for (int i = 0; i < inPoints.size()-1; i++) {
		for (int j = 0; j < inPoints[i].size()-1; j++) {
			// u, s ->
			//      /\
			// v, r |
			for (int r = 0; r < levels+2; r++) {
				v = (float)r / (float)(levels+1);
				for (int s = 0; s < levels+2; s++) {
					u = (float)s / (float)(levels+1);
					
					if (r == 1 && s == 1) {
						subdividedPoints[i*(levels+1)+r][j*(levels+1)+s] = inPoints[i][j]*0.5 + inPoints[i+1][j+1]*0.5;
					}
					else {
						subdividedPoints[i*(levels+1)+r][j*(levels+1)+s] = inPoints[i][j]*(1-u)*(1-v) + inPoints[i+1][j]*v*(1-u) + inPoints[i][j+1]*u*(1-v) + inPoints[i+1][j+1]*u*v;
					}
				}
			}
		}
	}
}

void GLWidget::btnOpenGL() {
	openGL = !openGL;	
}

void GLWidget::initialize(Scene* s) {
	scene = s;

	float controlSize = 4; //space between each control water point
	float size = g_CONTROL_SIZE / (subdivisions+1); //space between each water point (taking subdivisoins into account)

	waterPoints.resize(rows);
	for (int i = 0; i < rows; i++) {
		waterPoints[i].resize(cols);
		for (int j = 0; j < cols; j++) {
			WaterPoint w;

			//the water point is a control one
			if (i%(subdivisions+1) == 0 && j%(subdivisions+1) == 0) {
				//set the point as moving up or down
				int randNum = g_random(0, 1);
				if (randNum == 0) {
					w.p = Vector3(-j*size, -HEIGHT, i*size);
					w.up = true;
				}
				else {
					w.p = Vector3(-j*size, HEIGHT, i*size);
					w.up = false;
				}
				w.ratio = (float)g_random(70, 100)/100.0f;
				w.control = true;
			}
			//the water point is a subdivided one
			else {
				w.p = Vector3(-j*size, 0, i*size);
				w.control = false;
			}

			//set the point
			waterPoints[i][j] = w;
		}
	}

	//size the face normals
	faceNormals.resize(rows-1);
	for (int i = 0; i < faceNormals.size(); i++) {
		faceNormals[i].resize(cols-1);
	}

	//size the vertex normals
	vertexNormals.resize(rows);
	for (int i = 0; i < vertexNormals.size(); i++) {
		vertexNormals[i].resize(cols);
	}
	
	//simulate many iterations before animating so the program looks random as it should
	int iterations = 10000;
	for (int i = 0; i < iterations; i++) {
		//update without drawing
		update(0.05, false);
	}
}

void GLWidget::createCausticTriangles(vector<vector<Vector3> >& subdividedPoints) {
	Light light = scene->lights[0];
	Vector3 lightPos = light.position;

	float nAir = 1.0f;
	float nWater = 1.33f;

	Quad* quad = (Quad*)scene->objects[0];
	quad->causticTriangles.clear();
	Scene tmpScene;
	tmpScene.objects.push_back(new Plane(quad->center, quad->normal(Vector3())));

	vector<vector<Vector3> > causticPoints;
	causticPoints.resize(subdividedPoints.size());

	for (int i = 0; i < subdividedPoints.size(); i++) {
		causticPoints[i].resize(subdividedPoints[i].size());

		for (int j = 0; j < subdividedPoints[i].size(); j++) {
			Vector3 dir = subdividedPoints[i][j] - lightPos;
			dir.normalize();

			Vector3 transmitted = transmit(dir, vertexNormals[i][j], 1.3);

			Ray ray(subdividedPoints[i][j], transmitted, tmpScene);
			Intersection intersect = ray.intersect(ray.origin, ray.direction, false);

			causticPoints[i][j] = intersect.intersectionPoint;
		}
	}

	for (int i = 0; i < causticPoints.size()-1; i++) {
		for (int j = 0; j < causticPoints[i].size()-1; j++) {
			Triangle water1(subdividedPoints[i][j], subdividedPoints[i][j+1], subdividedPoints[i+1][j]);
			Triangle water2(subdividedPoints[i+1][j], subdividedPoints[i][j+1], subdividedPoints[i+1][j+1]);

			Triangle caustic1(causticPoints[i][j], causticPoints[i][j+1], causticPoints[i+1][j]);
			Triangle caustic2(causticPoints[i+1][j], causticPoints[i][j+1], causticPoints[i+1][j+1]);

			Vector3 water1ToLight = water1.p0*(1.0/3.0) + water1.p1*(1.0/3.0) + water1.p2*(1.0/3.0);
			water1ToLight = lightPos - water1ToLight;
			water1ToLight.normalize();

			if (caustic1.getArea() > 0.0000001) {
				caustic1.intensity = Vector3::dotProduct(water1.normal(), water1ToLight) * water1.getArea() / caustic1.getArea();
			}
			else {
				caustic1.intensity = 0;
			}

			Vector3 water2ToLight = water2.p0*(1.0/3.0) + water2.p1*(1.0/3.0) + water2.p2*(1.0/3.0);
			water2ToLight = lightPos - water2ToLight;
			water2ToLight.normalize();

			if (caustic2.getArea() > 0.0000001) {
				caustic2.intensity = Vector3::dotProduct(water2.normal(), water2ToLight) * water2.getArea() / caustic2.getArea();
			}
			else {
				caustic2.intensity = 0;
			}

			//float c1, c2;
			//c1 = caustic1.intensity;
			//c2 = caustic2.intensity;

			quad->causticTriangles.push_back(caustic1);
			quad->causticTriangles.push_back(caustic2);
		}
	}

	scene->objects[0] = quad;
}

Vector3 GLWidget::transmit(Vector3 d, Vector3 n, float nValue) {
	float n1, n2, nNew, c1, c2;

	c1 = -Vector3::dotProduct(n, d);

	n1 = nValue;
	n2 = 1.0f;

	nNew = n1/n2;

	c2 = sqrt(1 - nNew*nNew*(1 - c1*c1));

	return Vector3(d*nNew + n*(nNew*c1 - c2));
}
